---
title: "Prometheus-мониторинг: Разработка графиков Grafana"
type:
  - instruction
search: grafana разработка графиков
---

1. Для создания dashboard'ов в Grafana реализован ресурс [GrafanaDashboardDefinition](cr.html#grafanadashboarddefinition). У Pod'а с Grafana работает sidecar-контейнер с [shell-operator](https://github.com/flant/shell-operator), у которого есть хук, который следит за данным ресурсом. При соответствующем событии, хук создаёт/удаляет/правит соответствующий dashboard через специальный механизм.
2. В модулях манифесты dashboard лежат в директории `<module_root>/monitoring/grafana-dashboards/`. Они автоматически преобразуются в ресурсы [GrafanaDashboardDefinition](cr.html#grafanadashboarddefinition), при этом:
   * каждая поддиректория в этой директории соответствует Folder'у в Grafana,
   * а каждый файл — Dashboard'у в Grafana.
3. Если нужно добавить новый Folder, то просто создаём папку в каталоге `grafana-dashboards/` и добавляем туда хотя бы один JSON-манифест.
4. Править Dashboard'ы в файлах не нужно (если только это не какая-то совсем простая правка), вместо этого:
   * Открываем Dashboard в Grafana:
     * если это один из существующих — открываем его и [нажимаем кнопку Make editable](/docs/documentation/images/300-prometheus/grafana_make_editable.jpg);
     * если это новый — его необходимо просто создать (в любом Folder, это не важно, он потом переедет в тот, в который будет положен в репозитарии);
     * если это чужой готовый Dashboard — его можно импортировать в Grafana (там есть кнопка Import).
   * Меняем, меняем, меняем... до тех пор, пока не станем довольны Dashboard'ом. Лучше периодически нажимать на кнопку сохранить в Grafana чтобы, если браузер упадет, не потерять изменения.
   * [Выгружаем Dashboard в JSON](/docs/documentation/images/300-prometheus/grafana_export.jpg) и сохраняем в файл (новый или существующий).
5. Можно менять название Dashboard'а, название файла, переносить файл между Folder'ами — все подцепится автоматически.
6. Системные dashboard'ы должны хранится в ресурсах `GrafanaDashboardDefinition` с префиксом `d8` в имени.

## Как быстро привести чужой dashboard более-менее в порядок?

1. Найти все range'и и заменить на `$__interval_sx4`:

   ```shell
   for dashboard in *.json; do
     for range in $(grep '\[[0-9]\+[a-z]\]' $dashboard | sed 's/.*\(\[[0-9][a-z]\]\).*/\1/g' | sort | uniq); do
       sed -e 's/\['$range'\]/[$__interval_sx4]/g' -i $dashboard
     done
   done
   ```

2. Заменить `irate` на `rate`:

   ```shell
   sed 's/irate(/rate(/g' -i *.json
   ```

3. Заменить `Resolution` на `1/1`:

   ```shell
   sed 's/"intervalFactor":\s[0-9]/"intervalFactor": 1/' -i *.json
   ```

4. Убрать `Min Step`:

   ```shell
   sed '/"interval":/d' -i *.json
   ```

5. Заменить все графики на `Staircase` (поломает графики `Stack` + `Percent`, которые придется поправить руками на `Bars`):

   ```shell
   sed 's/"steppedLine": false/"steppedLine": true/' -i *.json
   ```

## Лучшие практики

### Сохраняйте uid Dashboard'а

При любых изменениях (в том числе при переименовании и переносе между Folder'ами) сохраняйте uid Dashboard'а (он обычно на одной из последних строк в JSON-файле). В этом случае все старые ссылки продолжат работать. Для этого никаких специальных действий не требуется, просто не меняйте намерено.

### Следите за изменениями в JSON-файлах

Редактировать JSON-файлы напрямую не стоит (это неудобно), а вот после внесения изменений в интерфейсе Grafana и выгрузки JSON-файла — обязательно стоит внимательно посмотреть, что изменилось, чтобы удостовериться, что случайно не поправили лишнего. Для этого есть `git add -p` и diff'ы в MR'ах, кому как удобней.

При изменении сложных Dashboard'ов, использующих функционал шаблонизирования, старайтесь редактировать Dashboard там же, где он был создан (обычно это <https://prometheus.kube.domain.my/>) и с теми же значениями в Variables — тогда в MR'е не будет множества лишних изменений "динамических данных".

### Что делать если мне нужно при отсутствии данных "прятать" дашборд или часть дашборда?

Не нужно пытаться генерировать Grafana Dashboard средствами Helm'а. Лучше заведите Issue в Grafana, чтобы сделали поддержку такого функционала.

## Требования к Dashboard'ам

### Job должен быть явно указан

Как и при разработке правил для Prometheus, необходимо всегда [явно указывать job](prometheus_rules_development.html#всегда-явно-указывать-job), чтобы гарантированно избежать конфликтов в именах метрик.

### Prometheus должен выбираться

В наших Grafana может быть доступно несколько Prometheus'ов (с разной степенью детализации данных и разным периодом хранения), а значит должна быть возможность удобного выбора сервера Prometheus. Чтобы это сделать, нужно:
* [создать](/docs/documentation/images/300-prometheus/grafana_ds_prometheus_variable.jpg) переменную `$ds_prometheus`
* в каждой панели [указать](/docs/documentation/images/300-prometheus/grafana_ds_prometheus_select_in_panel.jpg) именно `$ds_prometheus` в качестве data source, а не один из конкретных Prometheus'ов

### Graph Tooltip должен быть в режиме Shared crosshair

Одна из важнейших возможностей, которую предоставляют графики — возможность визуального анализа корреляций. Но, чтобы проводить такой анализ — нужно сопоставлять одну точку во времени на разных графиках, и если графики все одного размера и показаны друг под другом — это не создает проблем, но делать все графики одного размера не всегда возможно (и всегда некрасиво).

[Включайте](/docs/documentation/images/300-prometheus/grafana_graph_tooltip.jpg) Graph Tooltip всегда в режим Shared crosshair — он очень сильно помогает в визуальном анализе корреляций: стоит только навести мышь на один график, как на всех остальных появится полоса показывающая это время.

Использовать более навороченный режим Shared tooltip не рекомендуется, так как он перегружает лишней информацией.

### Единица измерения — это очень важно

График, на котором не указана единица измерения, всегда вводит в заблуждение! Что показано на графике — rpm или rps, биты или байты, ...? Всегда, когда вы смотрите на график, вы задаете вопрос — "Что показано на этом графике?", на который и отвечает правильно проставленная единица измерения. В Grafana есть возможность четко указать единицу измерения и это обязательно нужно делать.

Кроме этого, старайтесь использовать общепринятые единицы измерения, например:
* Скорость передачи данных принято измерять в битах в секунду, а не в байтах в секунду;
* Количество операций — обычно указывают в секунду, а не в минуту (iops, rps и пр.);
* Объем данных — всегда измеряют в байтах, а не в битах.

### Точность данных и детализация

Конечно, бывают случаи когда степень детализации мешает отследить большие тренды, но гораздо чаще происходит обратное — из-за недостаточной детализации не видно часть данных, необходимых для анализа. Чтобы показывать данные без потери точности:
1. **всегда используйте функцию `rate`, а не `irate`**;
2. **в качестве диапазона для range-векторов используйте `$__interval_sx4`**;
3. **всегда используйте Resolution 1/1**;
4. **никогда не устанавливайте Min step**.
5. **для функций avg/max/min_over_time в качестве диапазона для range-векторов используйте `$__interval_sx3`**;

![Точность данных и детализация](/docs/documentation/images/300-prometheus/grafana_accuracy.jpg)

{% offtopic title="Объяснение деталей и причин" %}
  <ul dir="auto">
    <li>В Prometheus в API в запросе можно указать <code>step</code>. Если мы загружаем данные за период в 3 часа и укажем <code>step</code> 30 секунд — получим 360 точек (3 часа *60 минут* 2 точки в минуту) и показать их на графике вполне реально. Однако если мы показываем данные за 24 часа — использовать шаг в 30 секунд уже нет никакого смысла, так как 2880 точек показать просто невозможно (если только на мониторе 4К полосками в один пиксель, но это ничего не дает, так-как человеческий глаз не может воспринимать такую информацию). Чтобы решить эту проблему, Grafana имеет хитрую механику автоматического определения <code>step</code> работающую примерно следующим образом:
    <ul>
      <li>В зависимости от размера графика (полная ширина, 1/2 ширины экрана, 1/4 ширины экрана и т.д.), размера окна браузера и разрешения экрана Grafana подсчитывает, сколько точек реально показать — например, на моем экране для графика на всю ширину Grafana считает нормальным показ 800 точек.</li>
      <li>Дальше она делит выбранный период показа на количество точек, которые реально показать, и получает "минимальный возможный для показа шаг" — например, на моем экране:
        <ul>
          <li>30 минут — 2.25 секунды,</li>
          <li>3 часа — 13.5 секунд,</li>
          <li>24 часа — 108 секунд,</li>
          <li>7 дней — 756 секунд.</li>
        </ul>
      </li>
      <li>Дальше Grafana смотрит настройки <code>Min step</code> и если он указан — делает <code>step</code> не меньше <code>Min step</code>. При этом <code>Min step</code> может быть указан в двух местах — глобально, в настройках data source'а и для каждого query в panel. У нас <code>Min step</code> указан глобально и соответствует <code>scrape_interval</code> у Prometheus (у основного это 30 секунд). Получаются следующие значения:
        <ul>
          <li>30 минут — 30 секунд (а не 2.25),</li>
          <li>3 часа — 30 секунд (а не 13.5 секунд),</li>
          <li>24 часа — 108 секунд,</li>
          <li>7 дней — 756 секунд.</li>
        </ul>
      </li>
      <li>Затем Grafana как-то хитро округляет получившиеся значения (до 5/15/30 секунд, 1/5/15 минут и т.д.), получаются следующие значения:
        <ul>
          <li>30 минут — 30 секунд,</li>
          <li>3 часа — 30 секунд,</li>
          <li>24 часа — 2 минуты,</li>
          <li>7 дней — 10 минут.</li>
        </ul>
      </li>
      <li>Затем Grafana смотрит параметр Resolution панели (там может быть 1/1, 1/2, ..., 1/10) и если там указано не 1/1 — увеличивает step (для 1/2 в два раза, для 1/10 в 10 раз).</li>
    </ul>
  </li>
  <li>Большая часть данных в Prometheus лежит в counter'ах (а не в gauge'ах), так что для получения текущего значения нужно использовать <code>rate</code> или <code>irate</code>. И тут начинается самое опасное и самое неприятное.
    <ul>
      <li>Функции <code>rate</code> и <code>irate</code> работают с range-векторами, но какой range передавать? В Grafana есть готовая переменная <code>$__interval</code> в которой уже хранится тот step, который будет отправлен в Prometheus, и казалось бы — используй!</li>
      <li>Но чтобы функции <code>rate</code> и <code>irate</code> работали — нужно чтобы в range-векторе обязательно было хотя бы две точки (что логично). А если мы возьмем range-вектор за 30 секунд в Prometheus со <code>scrape_interval</code> 30 секунд, в нем будет только одна точка и <code>rate</code>/<code>irate</code> ничего не выдадут. И тут есть множество НЕПРАВИЛЬНЫХ путей:
        <ul>
          <li>Кто-то ставит Resolution у всех query 1/2, чтобы в переменной <code>$__interval</code> при показе любого интервала было не меньше, чем 2 x <code>Min step</code>. Это помогает — графики начинают работать, но они всегда в два раза менее детальные, чем может хранит Prometheus.</li>
          <li>Кто-то ставит Min step равный двум <code>scrape_interval'ам</code> — это чуть лучше, но все равно шило на мыло.</li>
          <li>Кто-то начинает использовать <code>irate</code> и передает туда range-вектор за 1h (любое значение, заведомо больше периода) — и тут таится самый сильный подвох. В этом случае графики начинают показываться с абсолютной точностью, пока наш <code>step</code> меньше или равен <code>scrape_interval</code>, но если <code>step</code> больше <code>scrapte_interval</code> (показ данных за 24 часа и за 7 дней) — графит начинает жесточайше врать. Так как вместо того, чтобы показать в каждой точке rate за весь step, он показывает в каждой точке rate за последний <code>scrape_interval</code>. Проще говоря — получается, что при просмотре данных за 7 дней, вы видите на графике в каждой точке не среднее потребление CPU за 10 минут (<code>step</code>), а вы видите потребление за последние 30 секунд каждые 10 минут, а что было остальные 9 минут 30 секунд — просто не знаете!</li>
        </ul>
      </li>
      <li>Для решения этой проблемы мы пропатчили Grafana и добавили переменную <code>$__interval_sx4</code> (rv = range vector). Эта переменная равна <code>max($__interval, scrape_interval * 2)</code>, то есть в ней то же самое, что в <code>$__interval</code> но не меньше периода, в который попадет хотя бы две точки (<a href="https://github.com/grafana/grafana/issues/11451" rel="nofollow noreferrer noopener" target="_blank">мы завели тикет в Grafana об этом</a>). Это полностью решает проблему!</li>
    </ul>
  </li>
</ul>
{% endofftopic %}

#### Отсутствие данных нужно тоже точно показывать

У Grafana есть три режима показа Null Value (отсутствия данных).
* Никогда не используйте `connected`, он сильно вводит в заблуждение!
* Нужно использовать `null` во всех случаях, кроме стекирования — четко видно, что данных нет.
* При стекировании обычно нужно использовать `null as zero` (иначе при null в одной метрике пропадут все).

| connected               | null          | null as zero                  |
|-------------------------|---------------|-------------------------------|
| ![connected][connected] | ![null][null] | ![null as zero][null as zero] |

[connected]: /docs/documentation/images/300-prometheus/grafana_null_value_connected.jpg
[null]: /docs/documentation/images/300-prometheus/grafana_null_value_null.jpg
[null as zero]: /docs/documentation/images/300-prometheus/grafana_null_value_as_zero.jpg

#### Точность значения должна соответствовать целям

* Если процент ошибок измеряется в диапазоне от 0 до 10%, то какой смысл показывать 5 знаков после запятой? Все будут следить за целой частью и одним-двумя знаками после запятой.
* Если количество запросов прошедших за 3 часа (период по умолчанию для показа Dashboard'а) обычно измеряется десятками тысяч (а на некоторых кластерах может быть и сотнями миллионов) — стоит ли показывать точное количество запросов, или достаточно показать порядок?

Точность отображения значения должна соответствовать целям использования показателя, следите за этим!

#### По умолчанию показывать данные за последние 3 часа (автообновлять каждые 30 секунд)

Показ 3 часов является оптимальным по умолчанию, так как это максимальный период, который можно показать в полной детализации:
* При `scrape_interval` равном `30s` (это наше основное значение), при показе данных за 3 часа, данные показываются без аппроксимации и с полной детализацией — даже на графиках размером в 1/4 ширины экрана еще показываются все точки (данные за каждые 30 секунд).
* Больший масштаб (уменьшение периода показа) не имеет смысла, так как не увеличивает детализацию, но сужает диапазон видимости по времени.
* Меньший масштаб (увеличение периода показа) снижает детализацию и данные начинают аппроксимироваться.

Автоматическое обновление имеет смысл делать каждые 30 секунд, так как именно с такой частотой производится scrape и в Prometheus поступают новые данные.

#### Перед пушем новых дашбордов необходимо удалить упоминание реальных доменов

Перед пушем изменений в репозиторий необходимо убедиться, что домены, которые могли импортнуться из Grafana были удалены из json-файла.

Пример скрипта для удаления:

```shell
listOfDomains="
google.com
mycompany.com
"

listOfDashboards=$(find dashboards/* -name "*json")

for dashboard in $listOfDashboards; do
  for domain in $listOfDomains; do
    sed -i -E  "s/([^\"]+$domain)/example.com/g" $dashboard
  done
done
```

### TODO

* Использовать $__range и instant query, когда нужно посчитать данные за период показа (обычно это в singlestat'ах).
* Делать легенды одной ширины, чтобы графики были друг под другом.
* Проставлять ноль у оси Y.
* Указывать верхнюю границу у оси Y для графиков 0-100%.
* Как рисовать графики процентные (bars, вместо stepped lines).
* Трюк с использованием стекирования и показом Total.
* Не забывать ставить "On time range change", когда переменная получает значения, которые могут поменяться (например, имена Pod'ов).
* Цвета у CPU указывать как в okmeter.
* Когда использовать какой режим отрисовки и чем хорош staircase.
